package com.pt25.base.model.security;


import com.pt25.base.constants.BaseConstants;
import com.pt25.base.dep.redis.RedisService;
import com.pt25.base.exception.BizException;
import com.pt25.base.model.security.ddo.TokenData;
import com.pt25.base.model.security.ddo.TokenInnerData;
import com.pt25.base.util.JsonUtil;
import com.pt25.base.util.StrUtil;
import com.pt25.base.util.TokenUtil;
import com.pt25.base.web.WebSession;
import com.pt25.base.web.WebThreadLocal;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.redisson.api.RBatch;
import org.redisson.api.RedissonClient;
import org.redisson.client.codec.StringCodec;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import java.time.Duration;
import java.util.List;

/**
 * 安全模块核心工具类，主要提供的核心方法：
 * login: 用户登录
 * logout: 退出登录
 * getSessionData: 获取用户回话上下文信息
 */
@Component
public class SecurityService {

    private static final String pubKey = "@#pub-key%%&*()";

    @Resource
    private RedisService redisService;

    @Resource
    private SecurityProperties securityProperties;

    public String login(UserSession<?> userSession) {
        if (null != userSession) {
            checkSessionData(userSession);
            saveSessionData(userSession, true);
            return createTokenData(userSession);
        }
        throw new BizException("sessionData为null");
    }


    public void logout() {
        logout(getSessionData());
    }

    public UserSession<?> getSessionData() {
        UserSession<?> userSession = SecurityThreadLocal.get();
        if (null == userSession) {
            // 从请求头中获取Authorization信息
            RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
            if (null != requestAttributes) {
                HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
                if (null != request) {
                    String token = request.getHeader(BaseConstants.AUTHORIZATION);
                    return getSessionData(token);
                }
            }
        }
        return userSession;
    }

    public UserSession<?> getSessionData(String token) {
        if (null != token) {
            TokenData tokenData = getTokenData(token);
            TokenInnerData tokenInnerData = getTokenInnerData(tokenData);
            RedissonClient redissonClient = redisService.getRedissonClient(getRedisName(tokenData.getUserType()));
            Object sessionObj = redissonClient.getBucket(getSessionCacheKey(tokenData.getUserType(), tokenInnerData.getUid()), new StringCodec()).get();
            if (null != sessionObj) {
                UserSession<?> userSession = JsonUtil.toBean((String) sessionObj, UserSession.class);
                if (null != userSession) {
                    SecurityThreadLocal.set(userSession);
                    saveSessionData(userSession, false);
                    WebSession webSession = WebThreadLocal.get();
                    if (null != webSession && !webSession.getTenantId().equals(userSession.getTenantId())) {
                        throw new BizException("tenantId异常！");
                    }
                    return userSession;
                }
            }
        }
        throw new BizException("token解析异常！");
    }

    private void checkSessionData(UserSession<?> userSession) {
        if (StrUtil.isBlank(userSession.getUid())) {
            throw new BizException("uid不能为空！");
        }
        if (StrUtil.isBlank(userSession.getUserType())) {
            throw new BizException("userType不能为空！");
        }
        List<SecurityProperties.SecurityConfig> configs = securityProperties.getConfigs();
        for (SecurityProperties.SecurityConfig config : configs) {
            if (userSession.getUserType().equals(config.getUserType())) {
                return;
            }
        }
        throw new BizException("userType未定义!");
    }

    private void saveSessionData(UserSession<?> userSession, boolean isAddSession) {
        String sessionCacheKey = getSessionCacheKey(userSession);
        RedissonClient redissonClient = redisService.getRedissonClient(getRedisName(userSession));
        RBatch batch = redissonClient.createBatch();
        if (isAddSession) batch.getBucket(sessionCacheKey, new StringCodec()).setAsync(JsonUtil.toJson(userSession));
        batch.getBucket(sessionCacheKey, new StringCodec()).expireAsync(Duration.ofMinutes(userSession.getExpiredMinutes()));
        batch.execute();
    }


    private String getSessionCacheKey(UserSession<?> userSession) {
        return getSessionCacheKey(userSession.getUserType(), userSession.getUid());
    }

    private String getSessionCacheKey(String userType, String UID) {
        return "security-session&" + userType + "&" + UID;
    }

    private String getSecretKey(UserSession<?> userSession) {
        return getSecretKey(userSession.getUserType());
    }

    private String getSecretKey(String userType) {
        List<SecurityProperties.SecurityConfig> configs = securityProperties.getConfigs();
        for (SecurityProperties.SecurityConfig config : configs) {
            if (userType.equals(config.getUserType())) {
                return config.getSecretKey();
            }
        }
        throw new BizException("secretKey未定义!");
    }

    private String getRedisName(UserSession<?> userSession) {
        return getRedisName(userSession.getUserType());
    }

    private String getRedisName(String userType) {
        List<SecurityProperties.SecurityConfig> configs = securityProperties.getConfigs();
        for (SecurityProperties.SecurityConfig config : configs) {
            if (userType.equals(config.getUserType())) {
                return config.getRedisName();
            }
        }
        return BaseConstants.DEFAULT;
    }

    private String createTokenData(UserSession<?> userSession) {
        String encryptedInfo = createEncryptedInfo(userSession);
        TokenData tokenData = TokenData.builder().userType(userSession.getUserType()).encryptedInfo(encryptedInfo).build();
        return TokenUtil.encryptToken(tokenData, pubKey);
    }

    private String createEncryptedInfo(UserSession<?> userSession) {
        String secretKey = getSecretKey(userSession);
        TokenInnerData tokenInnerData = TokenInnerData.builder().uid(userSession.getUid()).build();
        return TokenUtil.encryptToken(tokenInnerData, secretKey);
    }

    /**
     * 使用公共秘钥解析token
     */
    private TokenData getTokenData(String token) {
        return TokenUtil.decryptToken(token, pubKey, TokenData.class);
    }

    /**
     * 使用不同的角色秘钥解析token
     */
    private TokenInnerData getTokenInnerData(TokenData tokenData) {
        return TokenUtil.decryptToken(tokenData.getEncryptedInfo(), getSecretKey(tokenData.getUserType()), TokenInnerData.class);
    }

    private void logout(UserSession<?> userSession) {
        String sessionCacheKey = getSessionCacheKey(userSession);
        RedissonClient redissonClient = redisService.getRedissonClient(getRedisName(userSession));
        redissonClient.getBucket(sessionCacheKey, new StringCodec()).delete();
    }

}
